package log

import (
	"fmt"
	"io"
	stdLog "log"
	"os"
	"runtime"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"sieve_admin_server_userpass/lib/config"
)

type Logger struct {
	logger   *stdLog.Logger
	offset   int
	wc       io.WriteCloser
	watchers []LogWatcher
}

type LogWatcher func(string, string, string)

var (
	defaultLogger = &Logger{
		logger:   stdLog.New(os.Stderr, "", 0),
		offset:   1,
		watchers: []LogWatcher{},
	}

	fLogToStderr = config.Bool("log.stderr", false, "output log to stderr too")
	// 旧格式 {Year}-{Month}-{Day} {Hour}:{Minute}:{Second}|{Level}|{Pos}|{Content}
	fLogFormat = config.String("log.format", "{LevelShort}|{Year}-{Month}-{Day} {Hour}:{Minute}:{Second}|{Pos}| {Content}", "日志输出格式")
)

func New(w io.Writer) *Logger {
	return &Logger{
		logger:   stdLog.New(w, "", 0),
		watchers: []LogWatcher{},
	}
}

// func (l *Logger) now() string {
// 	t := time.Now()
// 	Y, M, D := t.Date()
// 	h, m, s := t.Clock()
// 	return fmt.Sprintf("%4d-%02d-%02d %02d:%02d:%02d", Y, M, D, h, m, s)
// }

func (l *Logger) pos(skip int, short bool) string {
	_, file, line, ok := runtime.Caller(skip + l.offset)
	if !ok {
		file = "???"
		line = 0
	}
	if short {
		i := strings.LastIndex(file, "/")
		file = file[i+1:]
	}

	return file + ":" + strconv.Itoa(line)
}

func (l *Logger) log(lv, pos, data string) {
	t := time.Now()
	Y, M, D := t.Date()
	h, m, s := t.Clock()
	rep := strings.NewReplacer(
		"{Year}", fmt.Sprintf("%4d", Y),
		"{YearShort}", strconv.Itoa(Y),
		"{Month}", fmt.Sprintf("%02d", M),
		"{MonthShort}", strconv.Itoa(int(M)),
		"{Day}", fmt.Sprintf("%02d", D),
		"{DayShort}", strconv.Itoa(D),
		"{Hour}", fmt.Sprintf("%02d", h),
		"{HourShort}", strconv.Itoa(h),
		"{Minute}", fmt.Sprintf("%02d", m),
		"{MinuteShort}", strconv.Itoa(m),
		"{Second}", fmt.Sprintf("%02d", s),
		"{SecondShort}", strconv.Itoa(s),
		"{Level}", lv,
		"{LevelShort}", lv[:1],
		"{Pos}", pos,
		"{Content}", data,
	)

	l.logger.Print(rep.Replace(*fLogFormat))
	// l.logger.Printf("%s|%s|%s|%s", l.now(), lv, pos, data)
	if l.watchers != nil && len(l.watchers) > 0 {
		for _, lw := range l.watchers {
			lw(lv, pos, data)
		}
	}
	if lv == "FATAL" {
		os.Exit(1)
	}
}

func (l *Logger) Fatal(v ...any) {
	l.log("FATAL", l.pos(2, true), fmt.Sprintln(v...))
}

func Fatal(v ...any) {
	defaultLogger.Fatal(v...)
}

func showLog(log string, filter *[]string) bool {
	if *filter == nil || len(*filter) == 0 {
		return true
	}
	for _, f := range *filter {
		if strings.Contains(log, f) {
			return true
		}
	}
	return false
}

var (
	fLogErrEnable = config.Bool("log.err.enable", true, "是否启用ERR日志")
	fLogErrFilter = config.Strings("log.err.filter", []string{}, "只输出包含此内容的ERR日志")
)

func (l *Logger) Err(v ...any) {
	if !*fLogErrEnable {
		return
	}
	s := fmt.Sprintln(v...)
	if showLog(s, fLogErrFilter) {
		l.log("ERR", l.pos(2, true), s)
	}
}

func Err(v ...any) {
	defaultLogger.Err(v...)
}

var (
	fLogInfoEnable = config.Bool("log.info.enable", true, "是否启用INFO日志")
	fLogInfoFilter = config.Strings("log.info.filter", []string{}, "只输出包含此内容的INFO日志")
)

func (l *Logger) Info(v ...any) {
	if !*fLogInfoEnable {
		return
	}
	s := fmt.Sprintln(v...)
	if showLog(s, fLogInfoFilter) {
		l.log("INFO", "", s)
	}
}

func Info(v ...any) {
	defaultLogger.Info(v...)
}

var (
	fLogDebugEnable = config.Bool("log.debug.enable", false, "是否启用DEBUG日志")
	fLogDebugFilter = config.Strings("log.debug.filter", []string{}, "只输出包含此内容的DEBUG日志")
)

func (l *Logger) Debug(v ...any) {
	if !*fLogDebugEnable {
		return
	}
	s := fmt.Sprintln(v...)
	if showLog(s, fLogDebugFilter) {
		l.log("DEBUG", l.pos(2, true), s)
	}
}

func Debug(v ...any) {
	defaultLogger.Debug(v...)
}

func (l *Logger) RecoverPanic() {
	if err := recover(); err != nil {
		l.recoverPanic(err)
	}
}

func (l *Logger) recoverPanic(err any) {
	// 保持单行日志
	ss := strings.Split(string(debug.Stack()), "\n")
	stack := " @" + ss[0]
	list := []string{}
	for i := 1; i < len(ss); i++ {
		if i%2 == 1 {
			list = append(list, " <- "+strings.TrimSpace(ss[i]))
		} else {
			if strings.Contains(ss[i], "runtime/panic.go") {
				list = []string{}
			} else {
				list = append(list, " @"+strings.TrimSpace(ss[i]))
			}
		}
	}
	l.log("PANIC", l.pos(4, false), fmt.Sprint(err)+"|"+stack+fmt.Sprintln(strings.Join(list, "")))
}

func RecoverPanic() {
	// recover相对defer必须且只能相隔一层函数调用
	if err := recover(); err != nil {
		defaultLogger.recoverPanic(err)
	}
}

func (l *Logger) SetOutput(wc io.WriteCloser) {
	if l.wc != nil {
		l.wc.Close()
	}
	l.wc = wc
	var w io.Writer = wc
	if *fLogToStderr {
		w = io.MultiWriter(wc, os.Stderr)
	}
	l.logger.SetOutput(w)
}

func SetOutput(w io.WriteCloser) {
	defaultLogger.SetOutput(w)
}

func (l *Logger) AddWatcher(w LogWatcher) {
	l.watchers = append(l.watchers, w)
}

func AddWatcher(w LogWatcher) {
	defaultLogger.AddWatcher(w)
}
